<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebFlux流式传输演示</title>
    <link rel="icon"
          href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='75' font-size='75' fill='%231e3c72'>⚡</text></svg>">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
            min-height: 100vh;
            padding: 20px;
            color: #333;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
        }

        .header {
            text-align: center;
            color: white;
            margin-bottom: 30px;
            padding: 20px;
        }

        .header h1 {
            font-size: 2.5em;
            margin-bottom: 10px;
            text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
        }

        .header p {
            font-size: 1.1em;
            opacity: 0.9;
        }

        .tabs {
            display: flex;
            background: white;
            border-radius: 12px 12px 0 0;
            overflow: hidden;
            margin-bottom: -1px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
        }

        .tab {
            flex: 1;
            padding: 15px 20px;
            text-align: center;
            cursor: pointer;
            font-weight: 600;
            transition: all 0.3s ease;
            border-bottom: 3px solid transparent;
        }

        .tab.active {
            background: #f8f9fa;
            border-bottom: 3px solid #1e3c72;
            color: #1e3c72;
        }

        .tab:hover:not(.active) {
            background: #f0f0f0;
        }

        .card {
            background: white;
            border-radius: 0 0 12px 12px;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
            padding: 30px;
            margin-bottom: 30px;
            display: none;
        }

        .card.active {
            display: block;
            animation: fadeIn 0.5s ease;
        }

        @keyframes fadeIn {
            from {
                opacity: 0;
                transform: translateY(10px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }

        .controls {
            margin-bottom: 25px;
            display: flex;
            gap: 15px;
            flex-wrap: wrap;
            align-items: end;
        }

        .control-group {
            flex: 1;
            min-width: 200px;
        }

        .control-group label {
            display: block;
            margin-bottom: 8px;
            font-weight: 500;
            color: #555;
        }

        .control-group input, .control-group textarea {
            width: 100%;
            padding: 12px 15px;
            border: 2px solid #e0e0e0;
            border-radius: 6px;
            font-size: 1em;
            transition: all 0.3s ease;
        }

        .control-group textarea {
            min-height: 80px;
            resize: vertical;
        }

        .control-group input:focus, .control-group textarea:focus {
            outline: none;
            border-color: #1e3c72;
            box-shadow: 0 0 0 3px rgba(30, 60, 114, 0.1);
        }

        .button-group {
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
        }

        button {
            padding: 12px 20px;
            border: none;
            border-radius: 6px;
            font-size: 1em;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .btn-start {
            background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
            color: white;
            flex: 1;
            min-width: 120px;
            justify-content: center;
        }

        .btn-start:hover:not(:disabled) {
            transform: translateY(-2px);
            box-shadow: 0 5px 20px rgba(30, 60, 114, 0.4);
        }

        .btn-start:disabled {
            opacity: 0.6;
            cursor: not-allowed;
        }

        .btn-stop {
            background: #ff6b6b;
            color: white;
            flex: 1;
            min-width: 100px;
            justify-content: center;
        }

        .btn-stop:hover {
            background: #ff5252;
            transform: translateY(-2px);
        }

        .btn-clear {
            background: #f0f0f0;
            color: #333;
            flex: 1;
            min-width: 100px;
            justify-content: center;
        }

        .btn-clear:hover {
            background: #e0e0e0;
        }

        .status {
            margin-bottom: 20px;
            padding: 15px;
            border-radius: 8px;
            font-size: 0.95em;
            display: flex;
            align-items: center;
            gap: 12px;
        }

        .status.idle {
            background: #f0f0f0;
            color: #666;
        }

        .status.streaming {
            background: #e3f2fd;
            color: #1976d2;
        }

        .status.done {
            background: #e8f5e9;
            color: #388e3c;
        }

        .status.error {
            background: #ffebee;
            color: #d32f2f;
        }

        .status::before {
            content: '';
            display: inline-block;
            width: 12px;
            height: 12px;
            border-radius: 50%;
            flex-shrink: 0;
        }

        .status.idle::before {
            background: #999;
        }

        .status.streaming::before {
            background: #1976d2;
            animation: pulse 1.5s ease-in-out infinite;
        }

        .status.done::before {
            background: #388e3c;
        }

        .status.error::before {
            background: #d32f2f;
        }

        @keyframes pulse {
            0%, 100% {
                opacity: 1;
            }
            50% {
                opacity: 0.5;
            }
        }

        .output-container {
            position: relative;
            background: #0a1929;
            border: 2px solid #2a3a4d;
            border-radius: 8px;
            padding: 20px;
            min-height: 250px;
            max-height: 400px;
            overflow-y: auto;
            font-size: 1.1em;
            line-height: 1.8;
            word-break: break-word;
            white-space: pre-wrap;
            color: #64ffda;
            font-family: 'Courier New', monospace;
        }

        .output-container::-webkit-scrollbar {
            width: 8px;
        }

        .output-container::-webkit-scrollbar-track {
            background: #0a1929;
            border-radius: 4px;
        }

        .output-container::-webkit-scrollbar-thumb {
            background: #1e3c72;
            border-radius: 4px;
        }

        .output-container::-webkit-scrollbar-thumb:hover {
            background: #2a5298;
        }

        .cursor {
            display: inline-block;
            width: 2px;
            height: 1.2em;
            background: #64ffda;
            margin-left: 2px;
            animation: blink 1s ease-in-out infinite;
            vertical-align: text-bottom;
        }

        @keyframes blink {
            0%, 49% {
                opacity: 1;
            }
            50%, 100% {
                opacity: 0;
            }
        }

        .stats {
            margin-top: 20px;
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
            gap: 15px;
        }

        .stat-item {
            background: #f8f9fa;
            padding: 15px;
            border-radius: 8px;
            border-left: 4px solid #1e3c72;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
        }

        .stat-item h3 {
            font-size: 0.9em;
            color: #666;
            margin-bottom: 8px;
        }

        .stat-item .value {
            font-size: 1.4em;
            font-weight: 700;
            color: #1e3c72;
        }

        .comparison {
            background: white;
            border-radius: 12px;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
            padding: 30px;
            margin-top: 30px;
        }

        .comparison h2 {
            text-align: center;
            margin-bottom: 25px;
            color: #1e3c72;
        }

        .comparison-table {
            width: 100%;
            border-collapse: collapse;
        }

        .comparison-table th {
            background: #1e3c72;
            color: white;
            padding: 15px;
            text-align: left;
        }

        .comparison-table td {
            padding: 15px;
            border-bottom: 1px solid #eee;
        }

        .comparison-table tr:nth-child(even) {
            background: #f8f9fa;
        }

        .comparison-table tr:hover {
            background: #e3f2fd;
        }

        .log-line {
            margin: 5px 0;
            padding: 5px 0;
        }

        @media (max-width: 768px) {
            .header h1 {
                font-size: 1.8em;
            }

            .controls {
                flex-direction: column;
            }

            .control-group {
                min-width: 100%;
            }

            .card {
                padding: 20px;
            }

            .tabs {
                flex-direction: column;
            }
        }
    </style>
</head>
<body>
<div class="container">
    <div class="header">
        <h1>🚀 WebFlux流式传输演示</h1>
        <p>基于Reactor的响应式编程实现</p>
    </div>

    <div class="tabs">
        <div class="tab active" data-tab="text-stream">文本流</div>
        <div class="tab" data-tab="log-stream">日志流</div>
    </div>

    <!-- 文本流卡片 -->
    <div class="card active" id="text-stream-card">
        <div class="controls">
            <div class="control-group">
                <label>自定义文本内容</label>
                <textarea id="textContent" placeholder="请输入要流式输出的文本内容">WebFlux是Spring Framework 5.0中引入的响应式编程框架，它提供了完全非阻塞的编程模型，能够更好地处理高并发场景下的数据流传输。通过使用Reactor库中的Flux和Mono，我们可以轻松实现高效的流式数据传输。</textarea>
            </div>
            <div class="button-group">
                <button class="btn-start" onclick="startTextStream()">
                    <span>▶️</span> 开始流式输出
                </button>
                <button class="btn-clear" onclick="clearOutput('text-output')">🗑️ 清空</button>
            </div>
        </div>
        <div id="text-status" class="status idle">就绪</div>
        <div class="output-container" id="text-output"></div>
        <div class="stats">
            <div class="stat-item">
                <h3>字符数</h3>
                <div class="value" id="text-char-count">0</div>
            </div>
            <div class="stat-item">
                <h3>耗时</h3>
                <div class="value" id="text-duration">0ms</div>
            </div>
            <div class="stat-item">
                <h3>状态</h3>
                <div class="value" id="text-stream-status">就绪</div>
            </div>
        </div>
    </div>

    <!-- 日志流卡片 -->
    <div class="card" id="log-stream-card">
        <div class="controls">
            <div class="button-group">
                <button class="btn-start" onclick="startLogStream()">
                    <span>▶️</span> 开始日志流
                </button>
                <button class="btn-stop" onclick="stopLogStream()">
                    <span>⏹️</span> 停止
                </button>
                <button class="btn-clear" onclick="clearOutput('log-output')">🗑️ 清空</button>
            </div>
        </div>
        <div id="log-status" class="status idle">就绪</div>
        <div class="output-container" id="log-output"></div>
        <div class="stats">
            <div class="stat-item">
                <h3>日志条数</h3>
                <div class="value" id="log-count">0</div>
            </div>
            <div class="stat-item">
                <h3>运行时间</h3>
                <div class="value" id="log-duration">0s</div>
            </div>
            <div class="stat-item">
                <h3>状态</h3>
                <div class="value" id="log-stream-status">就绪</div>
            </div>
        </div>
    </div>

    <!-- 对比说明 -->
    <div class="comparison">
        <h2>WebFlux vs SSE 对比</h2>
        <table class="comparison-table">
            <thead>
            <tr>
                <th>特性</th>
                <th>WebFlux</th>
                <th>SSE</th>
            </tr>
            </thead>
            <tbody>
            <tr>
                <td>编程模型</td>
                <td>响应式编程（Reactor）、非阻塞异步、背压支持</td>
                <td>传统Servlet API、阻塞或非阻塞、无背压支持</td>
            </tr>
            <tr>
                <td>性能</td>
                <td>高并发处理能力强、资源利用率高、低延迟</td>
                <td>一般并发处理能力、资源消耗相对较高、延迟稍高</td>
            </tr>
            <tr>
                <td>适用场景</td>
                <td>大数据流处理、实时数据推送、微服务间通信、高并发API</td>
                <td>简单文本推送、浏览器通知、服务器状态更新</td>
            </tr>
            <tr>
                <td>复杂度</td>
                <td>学习曲线较陡峭、调试相对困难、需要理解响应式编程</td>
                <td>易于理解和使用、调试简单、概念直观</td>
            </tr>
            </tbody>
        </table>
    </div>
</div>

<script>
    // 全局变量
    let textStartTime = null;
    let textCharCount = 0;
    let logStartTime = null;
    let logCount = 0;
    let logAbortController = null;

    // Tab切换功能
    document.querySelectorAll('.tab').forEach(tab => {
        tab.addEventListener('click', () => {
            document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
            tab.classList.add('active');

            const tabId = tab.getAttribute('data-tab');
            document.querySelectorAll('.card').forEach(card => {
                card.classList.remove('active');
            });
            document.getElementById(`${tabId}-card`).classList.add('active');
        });
    });

    // 更新状态
    function updateStatus(elementId, status, message) {
        const el = document.getElementById(elementId);
        el.className = 'status ' + status;
        el.textContent = message;
    }

    // 更新时间显示
    function updateDuration(elementId, startTime) {
        if (startTime) {
            const duration = Date.now() - startTime;
            document.getElementById(elementId).textContent = duration + 'ms';
        }
    }

    // 清空输出
    function clearOutput(elementId) {
        document.getElementById(elementId).innerHTML = '';
        const statusId = elementId.replace('-output', '-status');
        updateStatus(statusId, 'idle', '就绪');
    }

    // 文本流功能
    async function startTextStream() {
        const content = document.getElementById('textContent').value;
        const url = `/test/webflux/stream-text?content=${encodeURIComponent(content)}`;

        clearOutput('text-output');
        textCharCount = 0;
        textStartTime = Date.now();

        updateStatus('text-status', 'streaming', '开始流式传输...');
        document.getElementById('text-stream-status').textContent = '传输中';

        try {
            const response = await fetch(url);
            const reader = response.body.getReader();
            const decoder = new TextDecoder();
            const output = document.getElementById('text-output');

            output.innerHTML = '<span class="cursor"></span>';

            while (true) {
                const {done, value} = await reader.read();

                if (done) {
                    const cursor = output.querySelector('.cursor');
                    if (cursor) cursor.remove();

                    updateStatus('text-status', 'done', '流式传输完成！');
                    document.getElementById('text-stream-status').textContent = '完成';
                    break;
                }

                const text = decoder.decode(value);
                const lines = text.split('\n');
                for (const line of lines) {
                    if (line.startsWith('data:')) {
                        const data = line.substring(5).trim();
                        if (data) {
                            const cursor = output.querySelector('.cursor');
                            if (cursor) cursor.remove();

                            output.innerHTML += data + '<span class="cursor"></span>';
                            textCharCount++;
                            document.getElementById('text-char-count').textContent = textCharCount;
                            updateDuration('text-duration', textStartTime);

                            output.parentElement.scrollTop = output.parentElement.scrollHeight;
                        }
                    }
                }
            }
        } catch (error) {
            updateStatus('text-status', 'error', '传输出错: ' + error.message);
            document.getElementById('text-stream-status').textContent = '错误';
        }
    }


    // 日志流功能
    async function startLogStream() {
        const url = '/test/webflux/log-stream';

        clearOutput('log-output');
        logCount = 0;
        logStartTime = Date.now();
        logAbortController = new AbortController();

        updateStatus('log-status', 'streaming', '开始日志流传输...');
        document.getElementById('log-stream-status').textContent = '传输中';

        try {
            const response = await fetch(url, {signal: logAbortController.signal});
            const reader = response.body.getReader();
            const decoder = new TextDecoder();
            const output = document.getElementById('log-output');

            while (true) {
                const {done, value} = await reader.read();

                if (done) {
                    updateStatus('log-status', 'done', '日志流传输完成！');
                    document.getElementById('log-stream-status').textContent = '完成';
                    break;
                }

                const text = decoder.decode(value);
                const lines = text.split('\n');
                for (const line of lines) {
                    if (line.startsWith('data:')) {
                        const data = line.substring(5).trim();
                        if (data) {
                            output.innerHTML += `<div class="log-line">${data}</div>`;
                            logCount++;
                            document.getElementById('log-count').textContent = logCount;

                            const elapsed = Math.floor((Date.now() - logStartTime) / 1000);
                            document.getElementById('log-duration').textContent = elapsed + 's';

                            output.parentElement.scrollTop = output.parentElement.scrollHeight;
                        }
                    }
                }
            }
        } catch (error) {
            if (error.name === 'AbortError') {
                updateStatus('log-status', 'idle', '已停止');
                document.getElementById('log-stream-status').textContent = '已停止';
            } else {
                updateStatus('log-status', 'error', '传输出错: ' + error.message);
                document.getElementById('log-stream-status').textContent = '错误';
            }
        }
    }

    // 停止日志流
    function stopLogStream() {
        if (logAbortController) {
            logAbortController.abort();
        }
    }

    // 页面加载完成后的初始化
    window.addEventListener('load', () => {
        clearOutput('text-output');
        clearOutput('log-output');
    });
</script>
</body>
</html>
